/**
 *
 * Module        : CC (Cyclometric Complexity)
 * Authors       : Ben Kwint & Arie van der Veek
 * Date          : November 13th 2011
 * Description   : Calculates the cyclometric complexity of a software system
 *                 written in Java. The calculator uses the JDT generator in
 *                 Eclipse to find all the java control flow statements
 *				   http://docs.oracle.com/javase/tutorial/java/nutsandbolts/flow.html
 *          
 *                 More info on CC can be found at:
 *                 http://en.wikipedia.org/wiki/Cyclometric_complexity
 *				   The counting method described in 
 *				   G.K. Gill and C.F. Kemerer. Cyclomatic complexity density and software maintenance
 *	 			   productivity. Software Engineering, IEEE Transactions on, 17(12):1284{1288, 1991.
 *				   is used
 */
module Metrics::CC

import lang::java::jdt::Java;
import lang::java::jdt::JDT;
import lang::java::jdt::JavaADT;

import Graph;
import Set;
import List;
import IO;

import Metrics::Regular::StringUtils;
import Constants;

/* This module calculates the Cyclomatic Complexity per method
	Cyclomatic complexity is calculated by counting all the control
	flow statements like if-then-else, for, do/while and case
*/

// Get the cyclometric complexity of the function given in the AstNode tree
public int methodCyclometricComplexity(AstNode tree)
{
	int cc = 1;// basecount of CC is always one for one default control flow.
	// all cases that have to be taken into account for the cyclometric complexity
	visit(tree) {
		// Conditionals
		case ifStatement(_, _, _): cc += 1;
		case switchCase(bool isDefault, _): cc += 1;
		case conditionalExpression(_, _, _): cc += 1;
		case infixExpression(str operator, _, _, _):  {if ((operator == "||") || (operator == "&&")) cc += 1;}
		// Branching statements
		case breakStatement(_) :cc += 1;
	    case continueStatement(_) :cc += 1;
		// Iterative loops
		case whileStatement(_, _): cc += 1;
		case forStatement(_, _, _, _):cc += 1;
		case enhancedForStatement(_, _, _):cc += 1;
		case doStatement(_, _): cc += 1;
		// Exception handling statements, these also cause extra execution paths. Except the finally
		case throwStatement(_): cc += 1;
		case catchClause(_, _): cc += 1;
	};
	return cc;
}

// get the cyclometric complexity of a the given location/project
// where the limit is set to 0, so all cyclometric complexities are given
//public rel[str, int] calculateCyclometricComplexity(loc l)
//{
//  return calculateCyclometricComplexity(l, 0);
//}

// get the cyclometric complexity of the given AstNodeRel where
// the limit is based on minLimit
public list[tuple[str,int]] calculateCyclometricComplexity(AstNodeRel astNodes, int minLimit)
{
    list[tuple[Entity, AstNode]] tree = toList(astNodes);
    list[tuple[str,int]] metricSet = [];
    
    for( n <- [0..(size(tree)-1)] )
    {
      // get all the functions in a row, one by one
      tuple[Entity name, AstNode tree] t = tree[n];
      
      // calculate where the function belongs and how it is called.
      // all package names, the class name and function name are used
      str name = getQualifiedMethodsName(t.name);
      
      // calculate the cyclometric complexity of the function
      int c = methodCyclometricComplexity(t.tree);
      // if the cyclometric complexity is greater or equal to the minLimit
      // add it to the new relation
      if( minLimit <= c )
      { // only add the metic if it is higher than the minLimit
        metricSet = metricSet + [<name,c>];
      }
    }
    // return the project list of function and cyclometric complexities
    return metricSet;
}
